using Microsoft.AspNetCore.Diagnostics;
using Microsoft.IdentityModel.Tokens;
using ValidationException = Digitalmes.Common.Exceptions.ValidationException;

namespace Digitalmes.WebApi.Infrastructure;

/// <summary>
/// 全局异常处理程序。
/// </summary>
public sealed class GlobalExceptionHandler : IExceptionHandler
{
    private readonly Dictionary<Type, Func<HttpContext, Exception, CancellationToken, Task>> _exceptionHandlers;

    public GlobalExceptionHandler()
    {
        _exceptionHandlers = new()
            {
                { typeof(ValidationException), HandleValidationException },
                { typeof(NotFoundException), HandleNotFoundException },
                { typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
                { typeof(ForbiddenAccessException), HandleForbiddenAccessException },
                { typeof(BusinessException), HandleBusinessException },
                { typeof(UserFriendlyException), HandleUserFriendlyException },
                { typeof(SecurityTokenExpiredException), HandleUnauthorizedAccessException },
            };
    }

    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        var exceptionType = exception.GetType();

        if (_exceptionHandlers.TryGetValue(exceptionType, out var value))
        {
            await value.Invoke(httpContext, exception, cancellationToken);
            return true;
        }

        return false;
    }

    private async Task HandleValidationException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        var exception = (ValidationException)ex;

        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;

        await httpContext.Response.WriteAsJsonAsync(new ValidationProblemDetails(exception.Errors)
        {
            Status = StatusCodes.Status400BadRequest,
            Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
        }, cancellationToken);
    }

    private async Task HandleNotFoundException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        var exception = (NotFoundException)ex;

        httpContext.Response.StatusCode = StatusCodes.Status404NotFound;

        await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = StatusCodes.Status404NotFound,
            Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
            Title = "The specified resource was not found.",
            Detail = exception.Message,
        }, cancellationToken);
    }

    private async Task HandleUnauthorizedAccessException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;

        await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = StatusCodes.Status401Unauthorized,
            Title = "Unauthorized",
            Type = "https://tools.ietf.org/html/rfc7235#section-3.1",
        }, cancellationToken);
    }

    private async Task HandleForbiddenAccessException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;

        await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = StatusCodes.Status403Forbidden,
            Title = "Forbidden",
            Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3",
        }, cancellationToken);
    }

    private async Task HandleBusinessException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        var exception = (BusinessException)ex;

        httpContext.Response.StatusCode = StatusCodes.Status200OK;

        await httpContext.Response.WriteAsJsonAsync(new ApiResult
        {
            Code = exception.Code ?? 0,
            Message = exception.Message,
        }, cancellationToken);
    }

    private async Task HandleUserFriendlyException(HttpContext httpContext, Exception ex, CancellationToken cancellationToken)
    {
        var exception = (UserFriendlyException)ex;

        httpContext.Response.StatusCode = StatusCodes.Status200OK;

        await httpContext.Response.WriteAsJsonAsync(new ApiResult
        {
            Code = exception.Code ?? 0,
            Message = exception.Message,
        }, cancellationToken);
    }
}
